package bueu.bexl;

import java.lang.reflect.InvocationTargetException;

import java.util.Collection;

import static bueu.bexl.utils.TypeUtils.booleanValue;
import static bueu.bexl.utils.TypeUtils.numberValue;
import static bueu.bexl.utils.TypeUtils.stringValue;

public class BexlCtx {

	private final static Class<?>[] PARAM_TYPES = { Object[].class };

	private Object root;
	
	public BexlCtx() {
		//TODO
	}
	
	public BexlCtx(final Object root) {
		this.root = root;
	}
	
	public Object getRoot() {
		return this.root;
	}

	public void setRoot(final Object root) {
		this.root = root;
	}
	
	public final Object get(String expr) throws BexlException {
		return Bexl.compile(expr).get(this, this.root);
	}
	
	public final Object[] list(String expr) throws BexlException {
		return Bexl.compile(expr).list(this, this.root);
	}

	public final void set(String expr, Object value) {
		BexlExpr compiled = Bexl.compile(expr);
		
		if (compiled instanceof ReferenceExpr) {
			((ReferenceExpr) compiled).setValue(this, this.root, value);
		} else {
			throw new BexlException("La expresion no es settable");
		}
	}
	
	public final void setForAll(String rootExpr, String getExpr, Object value) {
		final BexlExpr getCompiled = Bexl.compile(getExpr);
		
		if (!(getCompiled instanceof ReferenceExpr)) {
			throw new BexlException("La expresion no es settable");
		}

		for (Object item : list(rootExpr)) {
			final Object val = value instanceof BexlExpr? ((BexlExpr) value).get(this, item) : value;
			((ReferenceExpr) getCompiled).setValue(this, item, val);
		}
	}
	
	protected final Object invokeFunction(String name, Object[] params) throws BexlException {
		try {
			return this.getClass().getMethod(name + "_", PARAM_TYPES)
				.invoke(this, new Object[] { params });
		} catch (IllegalArgumentException e) {
			throw new BexlException(e);
		} catch (SecurityException e) {
			throw new BexlException(e);
		} catch (IllegalAccessException e) {
			throw new BexlException(e);
		} catch (InvocationTargetException e) {
			throw new BexlException(e);
		} catch (NoSuchMethodException e) {
			throw new BexlException(e);
		}
	}
	
	public final Object sum_(final Object[] params) {
		double value = 0;
		final Object[] array = BexlExpr.toArray(params[0]);
		final int length = array.length;
		
		for (int i = 0; i < length; i++) {
			value += numberValue(array[i]);
		}
		
		return value;
	}
	
	public final Object avg_(final Object[] params) {
		double value = 0;
		final Object[] array = BexlExpr.toArray(params[0]);
		final int length = array.length;
		
		for (int i = 0; i < length; i++) {
			value += numberValue(array[i]);
		}
		
		return value / length;
	}
	
	public final Object count_(final Object[] params) {
		final Object param = params[0];

		if (param instanceof Collection) {
			return ((Collection<?>) param).size();
		} 
		
		if (param.getClass().isArray()) {
			return ((Object[]) param).length;
		}

		return 1;
	}
	
	public final Object max_(final Object[] params) {
		Object max = null;
		final Object[] array = BexlExpr.toArray(params[0]);
		final int length = array.length;
		
		for (int i = 0; i < length; i++) {
			Object item = array[i];

			if (max == null || BexlExpr.compare(max, item) < 0) {
				max = item;
			}
		}
		
		return max;
	}
	
	public final Object min_(final Object[] params) {
		Object min = null;
		final Object[] array = BexlExpr.toArray(params[0]);
		final int length = array.length;
		
		for (int i = 0; i < length; i++) {
			Object item = array[i];

			if (min == null || BexlExpr.compare(min, item) > 0) {
				min = item;
			}
		}
		
		return min;
	}

	public final Object number_(final Object[] params) {
		return numberValue(params[0]);
	}
	
	public final Object string_(final Object[] params) {
		return stringValue(params[0]);
	}
	
	public final Object boolean_(final Object[] params) {
		return booleanValue(params[0]);
	}

	public final Object round_(final Object[] params) {
		return Math.round(numberValue(params[0]));
	}
	
	public final Object abs_(final Object[] params) {
		return Math.abs(numberValue(params[0]));
	}
	
	public final Object ceiling_(final Object[] params) {
		return Math.ceil(numberValue(params[0]));
	}
	
	public final Object floor_(final Object[] params) {
		return Math.floor(numberValue(params[0]));
	}
	
	public final Object length_(final Object[] params) {
		return stringValue(params[0]).length();
	}
	
	public final Object upperCase_(final Object[] params) {
		return stringValue(params[0]).toUpperCase();
	}
	
	public final Object lowerCase_(final Object[] params) {
		return stringValue(params[0]).toLowerCase();
	}
}
